package com.authine.cloudpivot.ext.controller;

import com.aliyun.oss.OSS;
import com.aliyun.oss.model.OSSObject;
import com.authine.cloudpivot.engine.api.model.organization.UserModel;
import com.authine.cloudpivot.engine.api.model.runtime.AttachmentModel;
import com.authine.cloudpivot.engine.enums.type.FileUploadType;
import com.authine.cloudpivot.web.api.controller.base.PermissionController;
import com.authine.cloudpivot.web.api.exception.PortalException;
import com.authine.cloudpivot.web.api.exception.ResultEnum;
import com.authine.cloudpivot.web.api.handler.CustomizedOrigin;
import com.authine.cloudpivot.web.api.oss.FileStorageConfiguration;
import com.authine.cloudpivot.web.api.oss.OSSClient;
import com.authine.cloudpivot.web.api.sftp.SFTPClient;
import com.authine.cloudpivot.web.api.util.ImageUtils;
import com.authine.cloudpivot.web.api.view.ResponseResult;
import com.authine.cloudpivot.web.api.view.system.FontTextVO;
import com.authine.cloudpivot.web.api.view.system.OssConfigVO;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.jcraft.jsch.ChannelSftp;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Api(value = "文件上传", tags = "系统管理::文件上传下载接口")
@Controller
@RequestMapping("/huatai/custom")
@Slf4j
@CustomizedOrigin(level = 0)
public class OSSCustomController extends PermissionController {

    public static final String STORAGE_METHOD_OSS = "oss";
    private static final String KEY_WORKFLOW_ATTACHMENT = "cloudpivot:fileupload:workflow_attachment";
    private static final String MIMETYPE_SUFFIX = "application/";
    private static final String IMAGE_SUFFIX = "image/";
    private static final String PIC_EXTENSION = ".jpg.JPG.png.PNG.jpeg.JPEG";
    /**
     * 文件上传黑名单。为保证安全，只要文件名中包含该黑名单内容即不允许上传
     */
    private static final String[] IGNORE_FILE_TYPES = {
            ".php",".php5",".php4",".php3",".php2",".php1",
            ".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",
            ".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",
            ".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",
            ".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf"};
    /**
     *系统默认允许的上传文件类型
     */
    private static List<String> FILE_TYPE_WHITE_LIST = Arrays.asList(
            ".jpg",".jpeg",".png",".gif",".bmp",".svg",".tif",
            ".docx",".xlsx",".pptx",".doc",".xls",".ppt",".wpt",".dot",".rtf",".txt",".xml",".pdf",
            ".rar",".zip",
            ".MP3",".WMA",".WAV",".MIDI",".CDA",".VI",".mov",".rmvb",".rm",".FLV",".mp4",".3GP"
    );
    @Autowired
    private OSSClient ossClient;
    @Autowired
    private SFTPClient sftpClient;
    /**
     * 用户扩展允许上传文件类型,配置使用英文逗号分隔形式：.ext1,.ext2
     */
    @Value("${cloudpivot.attachment.fileTypes:}")
    private String extendFileTypes;

    @Autowired
    private RedisTemplate<String, AttachmentModel> redisTemplate;

    public static String[] getFileInfo(String fileName){
        int index = fileName.lastIndexOf(".");
        String toPrefix="";
        String toSuffix="";
        if(index==-1){
            toPrefix=fileName;
        }else{
            toPrefix=fileName.substring(0,index);
            toSuffix=fileName.substring(index,fileName.length());
        }
        return new String[]{toPrefix,toSuffix};
    }

    @PostConstruct
    public void init () {
        if(StringUtils.isNotEmpty(extendFileTypes)){
            String[] split = extendFileTypes.split(",");
            ArrayList<String> whiteList = Lists.newArrayList();
            whiteList.addAll(Arrays.asList(split));
            whiteList.addAll(FILE_TYPE_WHITE_LIST);
            FILE_TYPE_WHITE_LIST = whiteList;
        }
    }

    @PostMapping(value = "/upload")
    @ResponseBody
    @ApiOperation(value = "oss 文件上传接口,注意：key 用 file")
    public ResponseResult<AttachmentModel> upload(
            @RequestParam MultipartFile file, Boolean isAddWatermark, String locationInfo, String fileName,String refId) throws Exception{
        if (StringUtils.isEmpty(fileName)) {
            fileName = file.getOriginalFilename();
        }
        //检查上传文件类型
        checkFileType(fileName);
        String fileNameTemplate = null;
        try {
            if (!Strings.isNullOrEmpty(fileName)) {
                log.debug(fileName);
                String suffix = fileName.substring(fileName.lastIndexOf('.') + 1);

                boolean flag = false;

                if (refId == null) {
                    refId = UUID.randomUUID().toString().replace("-", "");
                }else {
                    flag = true;
                }

                fileNameTemplate = refId.concat(fileName);

                byte[] targetBytes = null;
                Boolean isPic = checkImageFile(file);
                if (log.isDebugEnabled()) {
                    log.debug(
                            "isPic = {}, isAddWatermark = {}, locationInfo = {}",
                            isPic,
                            isAddWatermark,
                            locationInfo);
                }
                if (isPic && isAddWatermark != null && isAddWatermark) {
                    // 如果是图片，则根据配置加上水印
                    String userId = getUserId();
                    UserModel user = getOrganizationFacade().getUser(userId);
                    String userName = (user == null ? "" : user.getName());
                    LocalDateTime now = LocalDateTime.now();
                    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                    if (StringUtils.isNotEmpty(locationInfo) && locationInfo.length() > 40) {
                        // 支持最多40个字，超过的…表示
                        locationInfo = locationInfo.substring(0, 40) + "...";
                    }
                    targetBytes =
                            ImageUtils.drawTextInImg(
                                    file.getBytes(),
                                    new FontTextVO(
                                            Lists.newArrayList(
                                                    now.format(dateTimeFormatter) + " " + userName, locationInfo),
                                            9,
                                            "#FFFFFF",
                                            24,
                                            "SimHei",
                                            0d,
                                            0.64f));
                    if (log.isDebugEnabled()) {
                        log.debug("drawTextInImg is finished....");
                    }
                }
                if (FileStorageConfiguration.getFileUploadType() == FileUploadType.OSS) {
                    log.debug("the upload type is sso");
                    File newFile = new File(fileNameTemplate);
                    FileOutputStream os = FileUtils.openOutputStream(newFile);
                    if(isPic && isAddWatermark != null && isAddWatermark){
                        os.write(targetBytes);
                    }else {
                        os.write(file.getBytes());
                    }
                    os.close();
//                    file.transferTo(newFile);
                    // 上传到OSS
                    ossClient.fileUpload(refId, newFile);
                    newFile.delete();
                } else {
                    log.info("the upload type is sftp");
                    try {
                        if(targetBytes==null){
                            targetBytes = file.getBytes();
                        }
                        sftpClient.uploadFile(fileNameTemplate,new ByteArrayInputStream(targetBytes));
                    } catch (Exception e) {
                        log.error("", e);
                        throw e;
                    }
                }

                // {"refId":"xxx","name":"filename","fileSize":10000,"fileExtension":".pdf","mimeType":"application/pdf","storageMethod":"oss"}

                AttachmentModel resourceModel = null;


                if (flag) {
                    resourceModel = getBizObjectFacade().getAttachmentByRefId(refId);
                }
                if (resourceModel == null) {
                    resourceModel = new AttachmentModel();
                }

                resourceModel.setRefId(refId);
                resourceModel.setName(fileName);
                resourceModel.setFileExtension(".".concat(suffix));
                resourceModel.setFileSize(new Long(file.getSize()).intValue());
                if(PIC_EXTENSION.contains(suffix)){
                    //IE图片展示兼容性
                    if(".jpg.JPG".contains(suffix)){
                        suffix = "jpeg";
                    }
                    resourceModel.setMimeType(IMAGE_SUFFIX.concat(suffix));
                }else{
                    resourceModel.setMimeType(MIMETYPE_SUFFIX.concat(suffix));
                }
                redisTemplate.boundHashOps(KEY_WORKFLOW_ATTACHMENT).putIfAbsent(refId, resourceModel);
                redisTemplate.expire(KEY_WORKFLOW_ATTACHMENT, 24, TimeUnit.HOURS);
                return getOkResponseResult(resourceModel, "文件上传成功");
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new PortalException(
                    ResultEnum.OSS_UPLOAD_ERR.getErrCode(), ResultEnum.OSS_UPLOAD_ERR.getErrMsg());
        }
        throw new PortalException(
                ResultEnum.OSS_UPLOAD_ERR.getErrCode(), ResultEnum.OSS_UPLOAD_ERR.getErrMsg());
    }

    private void checkFileType(String fileName) throws Exception{
        String suffix = fileName.substring(fileName.lastIndexOf('.'));
        if(StringUtils.isEmpty(suffix)){
            throw new Exception(ResultEnum.FILE_TYPE_ILLEGAL.getErrCode()+"-"+ResultEnum.FILE_TYPE_ILLEGAL.getErrMsg());
        }
        //白名单包含后缀名
        boolean contain = FILE_TYPE_WHITE_LIST.contains(suffix) || FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase()) || FILE_TYPE_WHITE_LIST.contains(suffix.toUpperCase());
        if(!contain){
            throw new Exception(ResultEnum.FILE_TYPE_ILLEGAL.getErrCode()+"-"+ResultEnum.FILE_TYPE_ILLEGAL.getErrMsg());
        }
        //文件名包含黑名单
        for (String illegal :IGNORE_FILE_TYPES) {
            boolean containsIllegal = fileName.contains(illegal)||fileName.contains(illegal.toLowerCase())||fileName.contains(illegal.toUpperCase());
            if(containsIllegal){
                throw new Exception(ResultEnum.FILE_TYPE_ILLEGAL.getErrCode()+"-"+ResultEnum.FILE_TYPE_ILLEGAL.getErrMsg());
            }
        }
    }

    /**
     * 检查上传的文件是否图片
     *
     * @param file 上传的文件
     * @return Boolean
     */
    private Boolean checkImageFile(MultipartFile file) {
        try {
            // 根据图片内容判断是否为图片文件
            InputStream inputStream = file.getInputStream();
            BufferedImage bi = ImageIO.read(inputStream);
            if (bi != null) {
                return Boolean.TRUE;
            }
        } catch (Exception e) {
            log.debug("检查图片时异常, e = {}", e);
            return Boolean.FALSE;
        }
        return Boolean.FALSE;
    }

    @GetMapping(value = "/download")
    @ResponseBody
    @ApiOperation(value = "oss 文件下载接口")
    @ApiImplicitParam(
            name = "refId",
            value = "资源id",
            required = true,
            dataType = "String",
            paramType = "query")
    public byte[] download(@RequestParam String refId, HttpServletResponse response) {
        validateNotEmpty(refId, "refId 不能为空");
        if (refId.contains(".") && FileStorageConfiguration.getFileUploadType() == FileUploadType.FTP) {
            response.setCharacterEncoding(Charsets.UTF_8.name());
            String suffix = refId.substring(refId.lastIndexOf('.') + 1);
            if(PIC_EXTENSION.contains(suffix)){
                //IE图片展示兼容性
                if(".jpg.JPG".contains(suffix)){
                    suffix = "jpeg";
                }
                response.setContentType(IMAGE_SUFFIX.concat(suffix));
            }else{
                response.setContentType(MIMETYPE_SUFFIX.concat(refId.substring(refId.lastIndexOf('.') + 1)));
            }
            try {
                //判断是否是IE11
                String header = request.getHeader("User-Agent").toUpperCase();
                Boolean flag = header.contains("MSIE") || header.contains("TRIDENT") || header.contains("EDGE");
                if (flag) {
                    response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(refId, "UTF-8"));
                }else {
                    response.setHeader("Content-Disposition", "attachment; filename=" + new String(refId.getBytes("utf-8"), "ISO8859-1"));
                }
                sftpClient.downloadFile(refId,response);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
        AttachmentModel resourceModel = getBizObjectFacade().getAttachmentByRefId(refId);
        if (resourceModel == null) {
            Object object = redisTemplate.boundHashOps(KEY_WORKFLOW_ATTACHMENT).get(refId);
            resourceModel = (AttachmentModel) object;
        }
        if (resourceModel == null) {
            throw new PortalException(
                    ResultEnum.OSS_DOWNLOAD_PERMISSION_ERR.getErrCode(),
                    ResultEnum.OSS_DOWNLOAD_PERMISSION_ERR.getErrMsg());
        }
        if (FileStorageConfiguration.getFileUploadType() == FileUploadType.OSS) {
            log.debug("the download type is sso");
            OSS client = null;
            try {

                String ossKey = resourceModel.getRefId();
                OssConfigVO ossConfig = ossClient.getOssConfig();
                client = ossClient.getClient(ossConfig);
                OSSObject ossObject = client.getObject(ossConfig.getBucket(), ossKey);
                if (ossObject == null) {
                    throw new PortalException(ResultEnum.OSS_UPLOAD_ERR.getErrCode(), "OSS 文件不存在：refId=".concat(ossKey));
                }
                InputStream inputStream = ossObject.getObjectContent();
                response.setCharacterEncoding(Charsets.UTF_8.name());
                response.setContentType(resourceModel.getMimeType());
                response.setHeader(
                        "Content-Disposition",
                        "attachment; fileName=" + URLEncoder.encode(resourceModel.getName(), "UTF-8"));
                //response.getOutputStream().write(IOUtils.toByteArray(inputStream));
                IOUtils.copy(inputStream, response.getOutputStream());
            } catch (IOException e) {
                log.error("文件下载失败: {}", e.getMessage());
                throw new PortalException(ResultEnum.OSS_UPLOAD_ERR.getErrCode(), ResultEnum.OSS_UPLOAD_ERR.getErrMsg());
            } finally {
                if (client != null) {
                    client.shutdown();
                }
            }
        } else {
            log.debug("the download type is sftp");
            String fileName = resourceModel.getRefId().concat(resourceModel.getName());
            ChannelSftp sftp = null;
            InputStream inputStream = null;
            try {
                log.debug("fileName:{}",fileName);
                sftp = sftpClient.createSftp();
                inputStream = sftp.get(sftpClient.getFileName(fileName));
                byte[] bytes = inputStream2byte(inputStream);
                return bytes;
            } catch (Exception e) {
                log.error("error===", e);
            }finally {
                try {
                    if(sftp!=null){
                        sftpClient.disconnect(sftp);
                    }
                    inputStream.close();
                } catch (Exception e) {
                    log.error(e.getMessage(),e);
                }
            }
        }
        return  null;
    }


    public static byte[] inputStream2byte(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while ((rc = inputStream.read(buff, 0, 100)) > 0) {
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * URLEncoder
     *
     * @param value 参数
     * @return url
     */
    public String encodeURIComponent(String value) {
        try {
            return URLEncoder.encode(value, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    private void parseFileNames(List<String> fileNames, AttachmentModel resourceModel) {
        String fileName = null;
        String[] fileInfo = getFileInfo(resourceModel.getName());
        String toPrefix=fileInfo[0];
        String toSuffix=fileInfo[1];
        int i = 1;
        while (true) {
            if (fileNames.contains(resourceModel.getName())) {
                log.debug("文件名为{}",resourceModel.getName());
                fileName = toPrefix + "(" + i + ")" + toSuffix;
                resourceModel.setName(fileName);
                i ++;
            } else {
                fileNames.add(resourceModel.getName());
                break;
            }
        }
    }
}
